//
// Created by Yiming Zhang on 11/6/18.
//

//#include <stdlib.h>
#include <cstdlib>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <limits.h>
#include <linux/limits.h>
#include <errno.h>
#include <stdint.h>

#include "log.h"
#include "ursax.h"
#include "debug.h"

unsigned int __log_level = INIT_LOG_LEVEL;
int flag_reopen_output = 0;

static const char* level_table[]={" [ DEBUG", " [ INFO ", " [ WARN ", " [ERROR!", " [FATAL!"};

int log_fd = 1;

static char log_conf_file_name[PATH_MAX], log_output_file_name[PATH_MAX];

#define LOG_LEVEL_NAME_LEN 32
struct LocalLogLevelRecord
{
    char name[LOG_LEVEL_NAME_LEN];
    unsigned int* plevel;
    unsigned int level;
};

static int snprintf_log_header(char* buf, int n, int level)
{
    static struct tm tm;
    static int n_short_path = 0;
    static time_t time_last_log = 0;
    static int length;


    struct timeval tv;
    gettimeofday(&tv, NULL);
    time_t now = tv.tv_sec;
    int msec = tv.tv_usec / 1000;
    int usec = tv.tv_usec % 1000;
    if (unlikely(n_short_path == 1024))
    {
        full_path:
        n_short_path = 0;
        time_last_log = now;
        tm = *localtime(&now);
        length = snprintf(buf, n, "%04d-%02d-%02d %02d:%02d:%02d.%03d.%03d%s]: ",
                          tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
                          tm.tm_hour, tm.tm_min, tm.tm_sec, msec, usec,
                          level_table[level]);
        return length;
    }

    n_short_path++;
    uint32_t delta = now - time_last_log;
    time_last_log = now;
    if (unlikely(true || delta > 0))
    {
        tm.tm_sec += delta;
        if (unlikely(tm.tm_sec >= 60))
        {
            tm.tm_min += tm.tm_sec / 60;
            tm.tm_sec %= 60;
            if (unlikely(tm.tm_min >= 60))
            {
                goto full_path;
            }
            snprintf(buf + 14, n - 14, "%02d:%02d.%03d.%03d", tm.tm_min, tm.tm_sec, msec, usec);
        }else{ // tm_sec < 60
            snprintf(buf + 17, n - 17, "%02d.%03d.%03d", tm.tm_sec, msec, usec);
        }
    }

    // copy log level literal
    memcpy(buf+27, level_table[level], 8);

    return length;
}

#ifndef NATIVE_SOCKET
extern "C" void* st_thread_self(void);
#endif

unsigned char pid = 0;
bool in_print_call_stack = false;
void log_vprintf(unsigned int level, const char* func, const char* format, va_list arglist, char new_line)
{
    if (unlikely(flag_reopen_output))
    {
        flag_reopen_output = 0;
        log_reload_conf(false);
        file_rotate(log_output_file_name);
        log_reopen_output();
    }

    if (unlikely(level > FATAL))
    {
        level = FATAL;
    }

    static char buf[20480];
    int len = snprintf_log_header(buf, sizeof(buf), level);
#ifndef NATIVE_SOCKET
    len -= 2;
    len += snprintf(buf + len, sizeof(buf)-len, " %u-%p %s(): (extra:%s) ", pid,
            st_thread_self(), func, get_thread_magics());
#endif
    // if (likely(len < sizeof(buf)))	// it's guaranteed
    {
        len += vsnprintf(buf+len, sizeof(buf)-len, format, arglist);
    }

    if (likely(len < (int)sizeof(buf))) {
        if(new_line){
            buf[ len++ ] = '\n';
        }
    } else {//if (len >= sizeof(buf))
        len = sizeof(buf);
        buf[ len-1 ] = '\n';
    }

    int rwr = write(log_fd, buf, len);
    (void)rwr;

#ifndef NATIVE_SOCKET
    if(level >= WARN){
        wrap_print_call_stack(&in_print_call_stack);
    }
#endif
}

void log_printf(unsigned int level, const char* func, const char* format, ...)
{
    va_list arglist;
    va_start( arglist, format );
    log_vprintf(level, func, format, arglist, 1);
    va_end( arglist );
}



#define STR_ENDING(x) ((x)=='\0' || (x)=='\n')
static int find_kv(char** buf, char* key, char* value)
{
    char* p = *buf;
    while (isspace(*p) || *p=='\n') p++;					// skip leading spaces of key
    if (*p=='\0') return -1;								// if no key found
    char* key_start = p++;

    while (*p != '=' && !STR_ENDING(*p)) p++;				// find the equal mark
    if (*p != '=') return -1;								// if no equal mark
    char* equal_mark = p++;

    while (isspace(*p)) p++;								// skip leading space of value
    if (STR_ENDING(*p)) return -1;							// if no value found
    char* value_start = p++;

    while (!STR_ENDING(*p)) p++;							// find the line ending
    char* line_end = p;

    if (*p != '\0') p++;									// if we have a next line
    *buf = p;

    p = equal_mark - 1;
    while (isspace(*p)) p--;								// skip tailing spaces of key
    char* key_end = p;

    p = line_end - 1;
    while (isspace(*p)) p--;								// skip tailing spaces of value
    char* value_end = p;

    int key_length = key_end - key_start + 1;
    memcpy(key, key_start, key_length);
    key[key_length] = '\0';

    int value_length = value_end - value_start + 1;
    memcpy(value, value_start, value_length);
    value[value_length] = '\0';
    return 0;
}
#undef STR_ENDING

static int my_print_int(int x)
{
    if (x < 0) {
        write(log_fd, "-", 1);
        return my_print_int(-x);
    }

    if (x >= 10) {
        my_print_int(x / 10);
        x = x % 10;
    }

    static char table[] = "0123456789";
    return write(log_fd, table + x, 1);
}

#define WRITE_ERROR(msg) {					\
	static char m[] = msg;					\
	write(log_fd, m, sizeof(m) - 1);		\
	my_print_int(errno);					\
	write(log_fd, "\n", 1);					\
}

#define MAX_LLL 128
static struct LocalLogLevelRecord local_log_levels[MAX_LLL];

static int find_local_log_level_record(const char* name)
{
    int i;
    for (i=0; i<MAX_LLL; ++i)
    {
        if (local_log_levels[i].name[0] == '\0')
        {
            return i;
        }
        if (strcmp(local_log_levels[i].name, name) ==0)
        {
            return i;
        }
    }
    return MAX_LLL;
}

int __register_local_log_level(const char* name, unsigned int* plevel)
{
    int index = find_local_log_level_record(name);

    if (unlikely(index == MAX_LLL)) {
        LOG_ERROR("no enough space for local log level records");
        return -1;
    }

    // pre registered
    if(likely(local_log_levels[index].name[0] != '\0')) {
        *plevel = local_log_levels[index].level;
    }

    strncpy(local_log_levels[index].name, name, LOG_LEVEL_NAME_LEN-1);
    local_log_levels[index].plevel = plevel;
    // LOG_INFO("set name %s level %d plevel %p", name, *plevel);
    return 0;
}

int do_set_ones_log_level(char* name, unsigned int level, bool pre_register)
{
    if (unlikely(level > FATAL))
    {
        level = FATAL;
    }

    int index = find_local_log_level_record(name);
    if (unlikely(index == MAX_LLL))
        return -1;

    if(local_log_levels[index].name[0] == '\0')
    {
        // if (pre_register && level > 0)
        if (pre_register)
        {
            strncpy(local_log_levels[index].name, name, LOG_LEVEL_NAME_LEN - 1);
            local_log_levels[index].level = level;
            // LOG_INFO("set name %s level %d plevel %p", name, level, local_log_levels[index].plevel);
        }
    }
    else
    {
        if (local_log_levels[index].plevel){
            LOG_INFO("set name %s, level %d, plevel %p", name, level, local_log_levels[index].plevel);
            *local_log_levels[index].plevel = level;
        }
    }

    return 0;
}


// 每行一个k=v，可以有空格、空行，不支持注释
int log_reload_conf(bool pre_register)
{
    if (log_conf_file_name[0] == '\0')
        return -1;

    printf("log_conf_file_name: %s\n", log_conf_file_name);
    int fd = open(log_conf_file_name, O_RDONLY);
    if (unlikely(fd < 0)) {
        int _errno = errno; /* Save errno value */
        fprintf(stderr, "Failed opening config file '%s': %s\n", log_conf_file_name, strerror(_errno));
//		WRITE_ERROR("failed to open config file: ")
        return -1;
    }

    static char buf[4096];
    int len = read(fd, buf, sizeof(buf));
    close(fd);
    if (unlikely(len <= 0)) {
        WRITE_ERROR("failed to read config file: ")
        return -1;
    }

    if (likely(len < (int)sizeof(buf))) {
        buf[ len ] = '\0';
    }
    else {
        buf[ len-1 ] = '\0';
    }

    char name[128], value[128], *p = buf;
    while (find_kv(&p, name, value) == 0) {
        unsigned int level;
        int ret = sscanf(value, "%u", &level);
        if (ret != 1 )
            continue;
        do_set_ones_log_level(name, level, pre_register);
    }

    return 0;
}

int log_load_conf(const char* filename)
{
    if (filename == NULL || *filename == '\0' || strcmp(filename, "-") == 0)
        return -1;

    strcpy(log_conf_file_name, filename);
    return log_reload_conf(true);
}

static void sig_reload_log_levels(int sig)
{
    flag_reopen_output = 1;
    __log_level = 0;
    signal(sig, sig_reload_log_levels);
}

void log_install_signal_handler(int sig)
{
    if (sig <= 0)
        sig = SIGUSR2;

    signal(sig, sig_reload_log_levels);
}

int log_reopen_output()
{
    if (log_output_file_name[0] == '\0')
        return -1;

    int ret = open(log_output_file_name, O_RDWR | O_APPEND | O_CREAT | O_CLOEXEC, 0644);
    if (ret < 0)
    {
        WRITE_ERROR("failed to open log output file: ")
        return ret;
    }

    if (log_fd >= 3)
    { // close the fd, if it's not std{in|out|err}
        close(log_fd);
    }

    log_fd = ret;
    return 0;
}

int log_set_output(const char* filename)
{
    if (filename == NULL || *filename == '\0' || strcmp(filename, "-") == 0)
        return -1;

    strcpy(log_output_file_name, filename);
    return log_reopen_output();
}

static int fn_length;
static char fn0[PATH_MAX], fn1[PATH_MAX];

static inline char * add_generation(char* fn, unsigned int generation)
{
    if (generation == 0) {
        fn[fn_length] = '\0';
    } else {
        snprintf(fn + fn_length, sizeof(fn1) - fn_length, ".%u", generation);
    }
    return fn;
}

int file_rotate(char *path)
{
    if(access(path, F_OK) != 0)
        return -1;
    fn_length = strlen(path);
    strcpy(fn0, path);
    strcpy(fn1, path);

    int last_generation = 1; // not include
    while(true){
        if(0 != access(add_generation(fn0, last_generation), F_OK))
            break;
        last_generation++;
    }

    while(last_generation >= 1){
        add_generation(fn0, last_generation - 1);
        add_generation(fn1, last_generation);
        int ret = rename(fn0, fn1);
        if (ret < 0)
        {
            LOG_ERROR("fail to rename '%s' to '%s' due to %d: %s", fn0, fn1, errno, strerror(errno));
        }
        last_generation--;
    }
    return 0;
}